namespace ScrumSprintMonitor.UI.Wpf
{
    #region #using Directives

    using System;
    using System.Configuration;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Reflection;

    using Extensibility;

    using Infrastructure;
    using Infrastructure.Configuration;

    #endregion

    #region #using Directives

    #endregion

    public sealed class Settings : IConfigurationManager
    {
        private static Settings defaultInstance;
        private readonly Configuration appConfig;
        private readonly ConfigurationSectionGroup pluginSettingsSectionGroup;
        private readonly KeyValueConfigurationCollection settings;

        public Settings()
        {
            Assembly assembly = Assembly.GetEntryAssembly();

            if (assembly == null || DesignerProperties.GetIsInDesignMode(null))
            {
                // Must be in design-mode. Use a mock with the default application settings.

                IsReadOnly = true;

                var appSettings = new AppSettingsSection();
                this.settings = appSettings.Settings;

                foreach (var propertyInfo in GetType().GetProperties())
                {
                    object[] attributes = propertyInfo.GetCustomAttributes(typeof(DefaultSettingValueAttribute), false);
                    if (attributes != null && attributes.Length == 1)
                    {
                        var attribute = (DefaultSettingValueAttribute) attributes.First();

                        this.settings.Add(propertyInfo.Name, attribute.Value);
                    }
                }
            }
            else
            {
                string binFolder = Path.GetDirectoryName(assembly.Location);
                string scrAssemblyLocation;

                string scrLocation = Path.Combine(binFolder, "ScrumSprintMonitor.Wpf.Application.scr");
                if (File.Exists(scrLocation))
                {
                    scrAssemblyLocation = scrLocation;
                }
                else
                {
                    string exeLocation = Path.Combine(binFolder, "ScrumSprintMonitor.Wpf.Application.exe");
                    scrAssemblyLocation = exeLocation;
                }

                // Open App.Config of executable
                var fileMap = new ExeConfigurationFileMap {
                                                              ExeConfigFilename = scrAssemblyLocation + ".config"
                                                          };

                if (!File.Exists(fileMap.ExeConfigFilename))
                {
                    throw new InvalidOperationException(string.Format("Configuration file is missing at {0}.", fileMap.ExeConfigFilename));
                }

                // Test access level to the configuration file
                try
                {
                    using (File.OpenWrite(fileMap.ExeConfigFilename))
                    {
                        IsReadOnly = false;
                    }
                }
                catch (UnauthorizedAccessException)
                {
                    IsReadOnly = true;
                }

                this.appConfig = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
                this.settings = this.appConfig.AppSettings.Settings;
                this.pluginSettingsSectionGroup = this.appConfig.GetSectionGroup("pluginSettings");
            }
        }

        public static Settings Default
        {
            [DebuggerStepThrough]
            get { return defaultInstance ?? (defaultInstance = new Settings()); }
        }

        public bool IsReadOnly { get; private set; }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("tfs08.codeplex.com")]
        public string TfsServerName
        {
            [DebuggerStepThrough]
            get { return ReadSetting<string>("TfsServerName"); }
            [DebuggerStepThrough]
            set { WriteSetting("TfsServerName", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("https://tfs08.codeplex.com:443")]
        public string TfsServerUri
        {
            [DebuggerStepThrough]
            get { return ReadSetting<string>("TfsServerUri"); }
            [DebuggerStepThrough]
            set { WriteSetting("TfsServerUri", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("")]
        public string TfsServerUserName
        {
            [DebuggerStepThrough]
            get { return ReadSetting<string>("TfsServerUserName"); }
            [DebuggerStepThrough]
            set { WriteSetting("TfsServerUserName", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("")]
        public string TfsServerPassword
        {
            [DebuggerStepThrough]
            get { return ReadSetting<string>("TfsServerPassword"); }
            [DebuggerStepThrough]
            set { WriteSetting("TfsServerPassword", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue(".\\config\\SprintConfiguration.txt")]
        public string ConfigurationFilePath
        {
            [DebuggerStepThrough]
            get { return ReadSetting<string>("ConfigurationFilePath"); }
            [DebuggerStepThrough]
            set { WriteSetting("ConfigurationFilePath", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue(".\\config\\PresentationConfiguration.txt")]
        public string PresentationConfigurationFilePath
        {
            [DebuggerStepThrough]
            get { return ReadSetting<string>("PresentationConfigurationFilePath"); }
            [DebuggerStepThrough]
            set { WriteSetting("PresentationConfigurationFilePath", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue(".\\config\\team-info.xml")]
        public string TeamMembersConfigurationFilePath
        {
            [DebuggerStepThrough]
            get { return ReadSetting<string>("TeamMembersConfigurationFilePath"); }
            [DebuggerStepThrough]
            set { WriteSetting("TeamMembersConfigurationFilePath", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("True")]
        public bool EnableOutlookIntegration
        {
            [DebuggerStepThrough]
            get { return ReadSetting<bool>("EnableOutlookIntegration"); }
            [DebuggerStepThrough]
            set { WriteSetting("EnableOutlookIntegration", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("False")]
        public bool ConsiderOutlookTentativeAsFreeTime
        {
            [DebuggerStepThrough]
            get { return ReadSetting<bool>("ConsiderOutlookTentativeAsFreeTime"); }
            [DebuggerStepThrough]
            set { WriteSetting("ConsiderOutlookTentativeAsFreeTime", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("True")]
        public bool ReportUnestimatedSprintBacklogItems
        {
            [DebuggerStepThrough]
            get { return ReadSetting<bool>("ReportUnestimatedSprintBacklogItems"); }
            [DebuggerStepThrough]
            set { WriteSetting("ReportUnestimatedSprintBacklogItems", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("Sprinting")]
        public SprintMode ActiveSprintMode
        {
            [DebuggerStepThrough]
            get { return ReadSetting<SprintMode>("ActiveSprintMode"); }
            [DebuggerStepThrough]
            set { WriteSetting("ActiveSprintMode", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("True")]
        public bool UseMultipleMonitors
        {
            [DebuggerStepThrough]
            get { return ReadSetting<bool>("UseMultipleMonitors"); }
            [DebuggerStepThrough]
            set { WriteSetting("UseMultipleMonitors", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("True")]
        public bool OptInCEIP
        {
            [DebuggerStepThrough]
            get { return ReadSetting<bool>("OptInCEIP"); }
            [DebuggerStepThrough]
            set { WriteSetting("OptInCEIP", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("True")]
        public bool ShowBurndownChart
        {
            [DebuggerStepThrough]
            get { return ReadSetting<bool>("ShowBurndownChart"); }
            [DebuggerStepThrough]
            set { WriteSetting("ShowBurndownChart", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("False")]
        public bool WakeUpMachine
        {
            [DebuggerStepThrough]
            get { return ReadSetting<bool>("WakeUpMachine"); }
            [DebuggerStepThrough]
            set { WriteSetting("WakeUpMachine", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("08:00:00")]
        public TimeSpan TimeOfDayToWakeUpMachine
        {
            [DebuggerStepThrough]
            get { return ReadSetting("TimeOfDayToWakeUpMachine"); }
            [DebuggerStepThrough]
            set { WriteSetting("TimeOfDayToWakeUpMachine", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("False")]
        public bool SuspendMachine
        {
            [DebuggerStepThrough]
            get { return ReadSetting<bool>("SuspendMachine"); }
            [DebuggerStepThrough]
            set { WriteSetting("SuspendMachine", value); }
        }

        [ApplicationScopedSetting]
        [DebuggerNonUserCode]
        [DefaultSettingValue("18:30:00")]
        public TimeSpan TimeOfDayToSuspendMachine
        {
            [DebuggerStepThrough]
            get { return ReadSetting("TimeOfDayToSuspendMachine"); }
            [DebuggerStepThrough]
            set { WriteSetting("TimeOfDayToSuspendMachine", value); }
        }

        #region IConfigurationManager Members

        public T GetConfigurationValue<T>(string sectionName, string property, T defaultValue)
        {
            var clientSettingsSection = (ClientSettingsSection) this.pluginSettingsSectionGroup.Sections[sectionName];

            if (clientSettingsSection != null)
            {
                return (T) Convert.ChangeType(clientSettingsSection.Settings.Get(property).Value.ValueXml.InnerText, typeof(T));
            }

            return defaultValue;
        }

        #endregion

        public void Refresh()
        {
            defaultInstance = null;
        }

        private void GuardWriteAttemptOnReadOnly()
        {
            if (IsReadOnly)
            {
                throw new InvalidOperationException(string.Format("The configuration file is read-only.", this.appConfig.FilePath));
            }
        }

        public void Save()
        {
            GuardWriteAttemptOnReadOnly();

            // Save the changes in App.config file.
            this.appConfig.Save(ConfigurationSaveMode.Modified);

            // Force a reload of a changed section.
            ConfigurationManager.RefreshSection("appSettings");
        }

        public T ReadSetting<T>(string settingName)
        {
            KeyValueConfigurationElement setting = this.settings[settingName];
            string value;

            if (setting == null)
            {
                PropertyInfo propertyInfo = GetType().GetProperty(settingName);
                Debug.Assert(propertyInfo != null);

                var attr = (DefaultSettingValueAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(DefaultSettingValueAttribute));
                Debug.Assert(attr != null);

                value = attr.Value;
            }
            else
            {
                value = setting.Value;
            }

            Debug.Assert(value != null);

            if (typeof(T).IsEnum)
            {
                return (T) Enum.Parse(typeof(T), value);
            }

            return (T) Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
        }

        public TimeSpan ReadSetting(string settingName)
        {
            return TimeSpan.Parse(ReadSetting<string>(settingName));
        }

        public void WriteSetting(string settingName, object value)
        {
            GuardWriteAttemptOnReadOnly();

            KeyValueConfigurationElement setting = this.settings[settingName];
            string textualValue = Convert.ToString(value, CultureInfo.InvariantCulture);

            if (setting == null)
            {
                this.settings.Add(settingName, textualValue);
            }
            else
            {
                this.settings[settingName].Value = textualValue;
            }
        }
    }
}